﻿using BSF.BaseService.NScript.Compiler;
using BSF.BaseService.NScript.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;

namespace BSF.BaseService.NScript
{
    /// <summary>
    /// 编译参数设置
    /// </summary>
    public class CompilerParams
{
        /// <summary>
        /// 编译类型 
        /// </summary>
       public EnumCompilerMode EnumCompilerMode = EnumCompilerMode.Assembly;
        /// <summary>
        /// 编译源码类型 （文件还是文本）
        /// </summary>
       public EnumSourceType EnumSourceType = EnumSourceType.Code;
        /// <summary>
        /// 编译内容（文件路径或者Main主文件文本）
        /// </summary>
       public string CodeOrFileName;
}
    public static class NScriptHelper
    {
        /// <summary>
        /// 默认编译结果会缓存,只要传入Main主文件或者文本内容不变，则缓存不失效。
        /// 注意：
        /// 1）程序集模式：编译持续缓存失效(编译代码内容经常发生改变)，缓存不会回收，会造成内存慢慢溢出，内存.net默认无法释放，除非重启程序。
        /// 2）应用程序域方式：编译默认也会使用缓存(编译代码内容改变则失效)，缓存不会回收，但是通过out compilerResult,运行compilerResult.Dispose，会回收资源，释放缓存，内存就不会溢出了。
        ///    若非常频繁使用应用程序域方式编译后又手工Dispose释放，性能自然也不高，所以要自行根据实际情况自行缓存自行释放应用程序域。
        ///    使用应用程序域，涉及到跨域通信问题，建议使用[Serializable]标记。
        /// 3）Main方式，建议使用在运维环境。代码级别调用则不支持。
        /// </summary>
        /// <returns></returns>
        public static T Run<T>(CompilerParams compilerParams,
            string typeName, string methodName, Object[] param, out CompilerResult compilerResult)
        {
            if(string.IsNullOrEmpty(typeName))
                throw new NScriptException("typeName不能为空");
            if (string.IsNullOrEmpty(methodName))
                throw new NScriptException("methodName不能为空");
            if (compilerParams.EnumCompilerMode == EnumCompilerMode.Assembly)
            {
                compilerResult = RunCompiler(compilerParams);
                object obj = compilerResult.Assembly.CreateInstance(typeName);
                if (obj == null)
                    throw new NScriptException("反射对象实例为空");
                MethodInfo objMI = obj.GetType().GetMethod(methodName);
                if (objMI == null)
                    throw new NScriptException("反射对象方法为空");
                try
                {
                    var r = (T)objMI.Invoke(obj, param);
                    return r;
                }
                catch (Exception exp)
                {
                    throw new NScriptException("调用方法出错:" + exp.Message,exp);
                }
            }
            else if (compilerParams.EnumCompilerMode == EnumCompilerMode.AppDomian)
            {
                try
                {
                    compilerResult = RunCompiler(compilerParams);
                    AppDomainCallResult<T> result = compilerResult.AppDomianInfo.AppDomainAssemblyLoader.Call<T>(typeName, methodName, param);
                    if (result.AppDomainCallStatus == AppDomainCallStatus.Error)
                    {
                        throw new NScriptException(result.Message == null ? "" : result.Message);
                    }
                    return result.Data;
                }
                catch (Exception exp)
                {
                    throw exp;
                }
               
            }
            else if (compilerParams.EnumCompilerMode == EnumCompilerMode.Main)
            {
                throw new NScriptException("不支持动态调用采用Main模式运行,请运行BSF.BaseService.NScript采用命令运行");
            }

            compilerResult = null;
            return default(T);
        }

        /// <summary>
        /// 动态编译并返回编译结果
        /// 程序集模式:返回程序集。（代码不变则缓存，缓存无法释放，会慢慢的内存溢出）
        /// 应用程序域模式：返回应用程序域（代码不变则缓存，缓存无法释放，会慢慢的内存溢出），也可以通过CompilerResult.Dispose释放则不会内存溢出。
        /// Main模式:默认编译为exe,通过AppDomain.CurrentDomain.ExecuteAssembly可以直接打开exe运行。
        /// </summary>
        /// <param name="compilerParams"></param>
        /// <returns></returns>
        public static CompilerResult RunCompiler(CompilerParams compilerParams)
       {
            if (compilerParams.EnumCompilerMode == EnumCompilerMode.Assembly)
            {
                var compilerInfo = new CompilerInfo().Parse(compilerParams.EnumSourceType, compilerParams.CodeOrFileName);
                var compilerResult = new AssemblyCompiler().DoCompiler(compilerInfo);
                if (compilerResult.Assembly == null)
                    throw new NScriptException("编译后程序集为null,编译失败");
                return compilerResult;
            }
            else if (compilerParams.EnumCompilerMode == EnumCompilerMode.AppDomian)
            {
                    var compilerInfo = new CompilerInfo().Parse(compilerParams.EnumSourceType, compilerParams.CodeOrFileName);
                    var compilerResult = new AppDomainCompiler().DoCompiler(compilerInfo);
                    if (compilerResult.AppDomianInfo == null || compilerResult.AppDomianInfo.AppDomain == null)
                        throw new NScriptException("编译后应用程序域为null,编译失败");
                    if (compilerResult.AppDomianInfo == null || compilerResult.AppDomianInfo.AppDomainAssemblyLoader == null)
                        throw new NScriptException("编译后应用程序域'调用加载器'为null,编译失败");
                    return compilerResult;
            }
            else if (compilerParams.EnumCompilerMode == EnumCompilerMode.Main)
            {
                var compilerInfo = new CompilerInfo().Parse(compilerParams.EnumSourceType, compilerParams.CodeOrFileName);
                var compilerResult = new MainCompiler().DoCompiler(compilerInfo);
                if (compilerResult.Assembly == null)
                    throw new NScriptException("编译后程序集为null,编译失败");
                return compilerResult;
            }
            return null;
       }
        
    }
}
